/*
 * Copyright (C) 2015 Basil Miller
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package devlight.io.library;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Arc;
import ohos.agp.render.BlurDrawLooper;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.render.PathEffect;
import ohos.agp.render.PathMeasure;
import ohos.agp.render.SweepShader;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;
import ohos.app.Environment;
import ohos.global.resource.RawFileEntry;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.multimodalinput.event.TouchEvent;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

/**
 * Created by GIGAMOLE on 04.03.2016.
 */
public class ArcProgressStackView extends Component implements Component.DrawTask, Component.TouchEventListener,
        Component.EstimateSizeListener {
    // Default values
    private static final float DEFAULT_START_ANGLE = 270.0F;
    private static final float DEFAULT_SWEEP_ANGLE = 360.0F;
    private static final float DEFAULT_DRAW_WIDTH_FRACTION = 0.7F;
    private static final float DEFAULT_MODEL_OFFSET = 5.0F;
    private static final float DEFAULT_SHADOW_RADIUS = 30.0F;
    private static final float DEFAULT_SHADOW_DISTANCE = 15.0F;
    private static final float DEFAULT_SHADOW_ANGLE = 90.0F;
    private static final int DEFAULT_ANIMATION_DURATION = 350;
    private static final int DEFAULT_ACTION_MOVE_ANIMATION_DURATION = 150;

    // Max and min progress values
    private static final float MAX_PROGRESS = 100.0F;
    private static final float MIN_PROGRESS = 0.0F;

    // Max and min fraction values
    private static final float MAX_FRACTION = 1.0F;
    private static final float MIN_FRACTION = 0.0F;

    // Max and min end angle
    private static final float MAX_ANGLE = 360.0F;
    private static final float MIN_ANGLE = 0.0F;

    // Min shadow
    private static final float MIN_SHADOW = 0.0F;

    // Action move constants
    private static final float POSITIVE_ANGLE = 90.0F;
    private static final float NEGATIVE_ANGLE = 270.0F;
    private static final int POSITIVE_SLICE = 1;
    private static final int NEGATIVE_SLICE = -1;
    private static final int DEFAULT_SLICE = 0;
    private static final int ANIMATE_ALL_INDEX = -2;
    private static final int DISABLE_ANIMATE_INDEX = -1;

    // Default colors
    private static final int DEFAULT_SHADOW_COLOR = Color.getIntColor("#8C000000");

    // Start and end angles
    private float mStartAngle;
    private float mSweepAngle;

    // Progress models
    private List<Model> mModels = new ArrayList<>();

    // Progress and text paints
    private Paint mProgressPaint = new Paint();
    private Paint mTextPaint = new Paint();
    private Paint mLevelPaint = new Paint();

    // ValueAnimator and interpolator for progress animating
    private final AnimatorValue mProgressAnimator = new AnimatorValue();
    private AnimatorValue.StateChangedListener mAnimatorListener;
    private AnimatorValue.ValueUpdateListener mAnimatorUpdateListener;
    private int mInterpolator = Animator.CurveType.INVALID;
    private int mAnimationDuration;
    private float mAnimatedFraction;

    // Square size of view
    private int mSize;

    // Offsets for handling and radius of progress models
    private float mProgressModelSize;
    private float mProgressModelOffset;
    private float mDrawWidthFraction;
    private float mDrawWidthDimension;

    // Shadow variables
    private float mShadowRadius;
    private float mShadowDistance;
    private float mShadowAngle;

    // Boolean variables
    private boolean mIsAnimated;
    private boolean mIsShadowed;
    private boolean mIsRounded;
    private boolean mIsDragged;
    private boolean mIsModelBgEnabled;
    private boolean mIsShowProgress;
    private boolean mIsLeveled;

    // Colors
    private Color mShadowColor;
    private Color mTextColor;


    // Action move variables
    private int mActionMoveModelIndex = DISABLE_ANIMATE_INDEX;
    private int mActionMoveLastSlice = 0;
    private int mActionMoveSliceCounter;
    private boolean mIsActionMoved;

    // Text typeface
    private Font mTypeface;

    // Indicator orientation
    private IndicatorOrientation mIndicatorOrientation;


    public ArcProgressStackView(Context context) {
        this(context, null);
    }

    public ArcProgressStackView(Context context, AttrSet attrSet) {
        this(context, attrSet, 0);
    }

    public ArcProgressStackView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    public ArcProgressStackView(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
        init(attrSet);
    }

    private boolean hasAttr(AttrSet attrSet, String name) {
        return attrSet != null && attrSet.getAttr(name).isPresent();
    }

    private void init(AttrSet attrSet) {
        // Init CPSV
        // Retrieve attributes from xml
        initPaints();
        if (hasAttr(attrSet, Styleable.ANIMATED)) {
            setIsAnimated(attrSet.getAttr(Styleable.ANIMATED).get().getBoolValue());
        } else {
            setIsAnimated(true);
        }
        if (hasAttr(attrSet, Styleable.SHADOWED)) {
            setIsShadowed(attrSet.getAttr(Styleable.SHADOWED).get().getBoolValue());
        } else {
            setIsShadowed(true);
        }
        if (hasAttr(attrSet, Styleable.ROUNDED)) {
            setIsRounded(attrSet.getAttr(Styleable.ROUNDED).get().getBoolValue());
        } else {
            setIsRounded(false);
        }
        if (hasAttr(attrSet, Styleable.DRAGGED)) {
            setIsDragged(attrSet.getAttr(Styleable.DRAGGED).get().getBoolValue());
        } else {
            setIsDragged(false);
        }
        if (hasAttr(attrSet, Styleable.LEVELED)) {
            setIsLeveled(attrSet.getAttr(Styleable.LEVELED).get().getBoolValue());
        } else {
            setIsLeveled(false);
        }
        if (hasAttr(attrSet, Styleable.TYPEFACE)) {
            setTypeface(attrSet.getAttr(Styleable.TYPEFACE).get().getStringValue());
        }
        if (hasAttr(attrSet, Styleable.TEXT_COLOR)) {
            setTextColor(attrSet.getAttr(Styleable.TEXT_COLOR).get().getColorValue());
        } else {
            setTextColor(Color.WHITE);
        }
        if (hasAttr(attrSet, Styleable.SHADOW_RADIUS)) {
            setShadowRadius(attrSet.getAttr(Styleable.SHADOW_RADIUS).get().getIntegerValue());
        } else {
            setShadowRadius(DEFAULT_SHADOW_RADIUS);
        }
        if (hasAttr(attrSet, Styleable.SHADOW_DISTANCE)) {
            setShadowDistance(attrSet.getAttr(Styleable.SHADOW_DISTANCE).get().getIntegerValue());
        } else {
            setShadowDistance(DEFAULT_SHADOW_DISTANCE);
        }
        if (hasAttr(attrSet, Styleable.SHADOW_ANGLE)) {
            setShadowAngle(attrSet.getAttr(Styleable.SHADOW_ANGLE).get().getIntegerValue());
        } else {
            setShadowAngle(DEFAULT_SHADOW_ANGLE);
        }
        if (hasAttr(attrSet, Styleable.SHADOW_COLOR)) {
            setShadowColor(attrSet.getAttr(Styleable.SHADOW_COLOR).get().getColorValue());
        } else {
            setShadowColor(new Color(DEFAULT_SHADOW_COLOR));
        }
        if (hasAttr(attrSet, Styleable.ANIMATION_DURATION)) {
            setAnimationDuration(attrSet.getAttr(Styleable.ANIMATION_DURATION).get().getLongValue());
        } else {
            setAnimationDuration(DEFAULT_ANIMATION_DURATION);
        }
        if (hasAttr(attrSet, Styleable.START_ANGLE)) {
            setStartAngle(attrSet.getAttr(Styleable.START_ANGLE).get().getIntegerValue());
        } else {
            setStartAngle(DEFAULT_START_ANGLE);
        }
        if ((hasAttr(attrSet, Styleable.SWEEP_ANGLE))) {
            setSweepAngle(attrSet.getAttr(Styleable.SWEEP_ANGLE).get().getFloatValue());
        } else {
            setSweepAngle(DEFAULT_SWEEP_ANGLE);
        }
        if (hasAttr(attrSet, Styleable.MODEL_OFFSET)) {
            setProgressModelOffset(attrSet.getAttr(Styleable.MODEL_OFFSET).get().getFloatValue());
        } else {
            setProgressModelOffset(DEFAULT_MODEL_OFFSET);
        }
        if (hasAttr(attrSet, Styleable.MODEL_BG_ENABLED)) {
            setModelBgEnabled(attrSet.getAttr(Styleable.MODEL_BG_ENABLED).get().getBoolValue());
        } else {
            setModelBgEnabled(false);
        }
        if (hasAttr(attrSet, Styleable.SHOW_PROGRESS)) {
            setShowProgress(attrSet.getAttr(Styleable.SHOW_PROGRESS).get().getBoolValue());
        } else {
            setShowProgress(true);
        }
        // Set orientation
        int orientationOrdinal = 0;
        if (hasAttr(attrSet, Styleable.INDICATOR_ORIENTATION)) {
            orientationOrdinal = attrSet.getAttr(Styleable.INDICATOR_ORIENTATION).get().getIntegerValue();
        }
        setIndicatorOrientation(orientationOrdinal == 0 ? IndicatorOrientation.VERTICAL : IndicatorOrientation.HORIZONTAL);
        // Retrieve interpolator
        if (hasAttr(attrSet, Styleable.INTERPOLATOR)) {
            mInterpolator = attrSet.getAttr(Styleable.INTERPOLATOR).get().getIntegerValue();
        }
        // Set animation info if is available
        mProgressAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float value) {
                mAnimatedFraction = value;
                if (mAnimatorUpdateListener != null) {
                    mAnimatorUpdateListener.onUpdate(animatorValue, value);
                }
                postInvalidate();
            }
        });
        // Check whether draw width dimension or fraction
        if (hasAttr(attrSet, Styleable.DRAW_WIDTH)) {
            int drawWidth = attrSet.getAttr(Styleable.DRAW_WIDTH).get().getDimensionValue();
            if (drawWidth == 0) {
                setDrawWidthFraction(attrSet.getAttr(Styleable.DRAW_WIDTH).get().getFloatValue());
            } else {
                setDrawWidthDimension(drawWidth);
            }
        } else {
            setDrawWidthFraction(DEFAULT_DRAW_WIDTH_FRACTION);
        }
        setEstimateSizeListener(this);
        addDrawTask(this);
        setTouchEventListener(this);

    }


    private void initPaints() {
        mProgressPaint.setDither(true);
        mProgressPaint.setStyle(Paint.Style.STROKE_STYLE);
        mProgressPaint.setAntiAlias(true);
        mTextPaint.setDither(true);
        mTextPaint.setTextAlign(TextAlignment.LEFT);
        mTextPaint.setAntiAlias(true);
        mLevelPaint.setDither(true);
        mLevelPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
        mLevelPaint.setPathEffect(new PathEffect(0.5F));
        mLevelPaint.setAntiAlias(true);
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        final int width = EstimateSpec.getSize(widthMeasureSpec);
        final int height = EstimateSpec.getSize(heightMeasureSpec);
        // Get size for square dimension
        if (width > height) {
            mSize = height;
        } else {
            mSize = width;
        }
        // Get progress offsets
        final float divider = mDrawWidthFraction == 0 ? mDrawWidthDimension : mSize * mDrawWidthFraction;
        mProgressModelSize = divider / mModels.size();
        final float paintOffset = mProgressModelSize * 0.5F;
        final float shadowOffset = mIsShadowed ? (mShadowRadius + mShadowDistance) : 0.0F;
        // Set bound with offset for models
        for (int i = 0; i < mModels.size(); i++) {
            final Model model = mModels.get(i);
            final float modelOffset = (mProgressModelSize * i) +
                    (paintOffset + shadowOffset) - (mProgressModelOffset * i);
            // Set bounds to progress
            model.mBounds.modify(modelOffset, modelOffset,
                    mSize - modelOffset, mSize - modelOffset);
            // Set sweep gradient shader
            if (model.getColors() != null) {
                model.mSweepGradient = new SweepShader(model.mBounds.getCenter().getPointX(),
                        model.mBounds.getCenter().getPointY(), model.getColors(), null);
            }
        }
        // Set square measured dimension
        setEstimatedSize(EstimateSpec.getSizeWithMode(mSize, EstimateSpec.PRECISE),
                EstimateSpec.getSizeWithMode(mSize, EstimateSpec.PRECISE));
        return true;
    }


    @Override
    public void onDraw(Component component, Canvas canvas) {
        // Save and rotate to start angle
        canvas.save();
        final float radius = mSize * 0.5F;
        canvas.rotate(mStartAngle, radius, radius);

        // Draw all of progress
        for (int i = 0; i < mModels.size(); i++) {
            final Model model = mModels.get(i);
            // Get progress for current model
            float progressFraction = mIsAnimated ? (model.mLastProgress + (mAnimatedFraction *
                    (model.getProgress() - model.mLastProgress))) / MAX_PROGRESS :
                    model.getProgress() / MAX_PROGRESS;
            if (i != mActionMoveModelIndex && mActionMoveModelIndex != ANIMATE_ALL_INDEX) {
                progressFraction = model.getProgress() / MAX_PROGRESS;
            }
            final float progress = progressFraction * mSweepAngle;
            final boolean isGradient = model.getColors() != null;
            drawProgress(canvas, model, progress, isGradient);

            final float progressLength = (float) (Math.PI / 180.0F) * progress * model.mBounds.getWidth() * 0.5F;

            float titleHorizontalOffset = drawTitle(canvas, model, progressLength);

            // Get pos and tan at final path point
            model.mPathMeasure.setPath(model.mPath, false);
            model.mPathMeasure.getPosTan(model.mPathMeasure.getLength(), model.mPos, model.mTan);

            // Get title width
            final float titleWidth = model.mTextBounds.getWidth();
            // Create model progress like : 23%
            final String percentProgress = String.format("%d%%", (int) model.getProgress(), Locale.ENGLISH);
            // Get progress text bounds
            mTextPaint.setTextSize((int) (mProgressModelSize * 0.35f));
            model.mTextBounds.modify(mTextPaint.getTextBounds(percentProgress));

            // Get pos tan with end point offset and check whether the rounded corners for offset
            final float progressHorizontalOffset =
                    mIndicatorOrientation == IndicatorOrientation.VERTICAL ?
                            model.mTextBounds.getHeight() * 0.5F : model.mTextBounds.getWidth() * 0.5F;
            final float indicatorProgressOffset = (mIsRounded ? progressFraction : 1.0F) *
                    (-progressHorizontalOffset - titleHorizontalOffset
                            - (mIsRounded ? model.mTextBounds.getHeight() * 2.0F : 0.0F));
            model.mPathMeasure.getPosTan(
                    model.mPathMeasure.getLength() + indicatorProgressOffset, model.mPos,
                    mIndicatorOrientation == IndicatorOrientation.VERTICAL && !mIsRounded ?
                            new float[2] :
                            model.mTan
            );

            // Check if there available place for indicator
            if ((titleWidth + model.mTextBounds.getHeight() + titleHorizontalOffset * 2.0F) -
                    indicatorProgressOffset < progressLength) {
                // Get rotate indicator progress angle for progress value
                float indicatorProgressAngle =
                        (float) (Math.atan2(model.mTan[1], model.mTan[0]) * (180.0F / Math.PI));
                // Get arc angle of progress indicator
                final float indicatorLengthProgressAngle = ((progressLength + indicatorProgressOffset) /
                        (model.mBounds.getWidth() * 0.5F)) * (float) (180.0F / Math.PI);

                // Detect progress indicator position : left or right and then rotate
                if (mIndicatorOrientation == IndicatorOrientation.VERTICAL) {
                    // Get X point of arc angle progress indicator
                    final float x = (float) (model.mBounds.getWidth() * 0.5F *
                            (Math.cos((indicatorLengthProgressAngle + mStartAngle) *
                                    Math.PI / 180.0F))) + model.mBounds.getCenter().getPointX();
                    indicatorProgressAngle += (x > radius) ? -90.0F : 90.0F;
                } else {
                    // Get Y point of arc angle progress indicator
                    final float y = (float) (model.mBounds.getHeight() * 0.5F *
                            (Math.sin((indicatorLengthProgressAngle + mStartAngle) *
                                    Math.PI / 180.0F))) + model.mBounds.getCenter().getPointY();
                    indicatorProgressAngle += (y > radius) ? 180.0F : 0.0F;
                }

                // Draw progress value
                canvas.save();
                canvas.rotate(indicatorProgressAngle, model.mPos[0], model.mPos[1]);
                if (mIsShowProgress) {
                    canvas.drawText(
                            mTextPaint, percentProgress,
                            model.mPos[0] - model.mTextBounds.getPreciseCenter().getPointX(),
                            model.mPos[1] - model.mTextBounds.getPreciseCenter().getPointY()
                    );
                }
                canvas.restore();
            }
            // Check if gradient and have rounded corners, because we must to create elevation effect
            // for start progress corner
            if ((isGradient || mIsLeveled) && mIsRounded && progress != 0) {
                model.mPathMeasure.getPosTan(0.0F, model.mPos, model.mTan);
                // Set paint for overlay rounded gradient with shadow
                setLevelShadowLayer();
                //noinspection ResourceAsColor
                mLevelPaint.setColor(isGradient ? model.getColors()[0] : model.getColor());
                // Get bounds of start pump
                final float halfSize = mProgressModelSize * 0.5F;
                final RectFloat arcRect = new RectFloat(
                        model.mPos[0] - halfSize, model.mPos[1] - halfSize,
                        model.mPos[0] + halfSize, model.mPos[1] + halfSize + 2.0F
                );
                Arc arc = new Arc(0.0F, -180.0F, true);
                canvas.drawArc(arcRect, arc, mLevelPaint);
            }
        }
        // Restore after drawing
        canvas.restore();
    }

    private void drawProgress(Canvas canvas, Model model, float progress, boolean isGradient) {
        // Set width of progress
        mProgressPaint.setStrokeWidth(mProgressModelSize);
        // Set model arc progress
        model.mPath.reset();
        model.mPath.addArc(model.mBounds, 0.0F, progress);
        // Draw gradient progress or solid
        resetShadowLayer();
        mProgressPaint.setShader(null, Paint.ShaderType.GROUP_SHADER);
        mProgressPaint.setStyle(Paint.Style.STROKE_STYLE);
        if (mIsModelBgEnabled) {
            //noinspection ResourceAsColor
            mProgressPaint.setColor(model.getBgColor());
            Arc arc = new Arc(0.0F, mSweepAngle, false);
            canvas.drawArc(model.mBounds, arc, mProgressPaint);
            mProgressPaint.clearBlurDrawLooper();
        }

        // Check if gradient for draw shadow at first and then gradient progress
        // Check if model have gradient
        if (isGradient) {
            if (!mIsModelBgEnabled) {
                canvas.drawPath(model.mPath, mProgressPaint);
                mProgressPaint.clearBlurDrawLooper();
            }
            mProgressPaint.setShader(model.mSweepGradient, Paint.ShaderType.SWEEP_SHADER);
        } else {
            mProgressPaint.setColor(model.getColor());
        }
        // Here we draw main progress
        mProgressPaint.setAlpha(1);
        canvas.drawPath(model.mPath, mProgressPaint);
    }

    private float drawTitle(Canvas canvas, Model model, float progressLength) {
        // Get model title bounds
        mTextPaint.setTextSize((int) (mProgressModelSize * 0.5F));
        model.mTextBounds.modify(mTextPaint.getTextBounds(model.getTitle()));
        // Draw title at start with offset
        final float titleHorizontalOffset = model.mTextBounds.getHeight() * 0.5F;

        String title = model.getTitle();
        int avail = (int) (progressLength - titleHorizontalOffset * (mIsRounded ? 0 : 2));
        if (avail > 0 && avail < model.mTextBounds.getWidth()) {
            title = title.substring(0, avail / mTextPaint.getTextSize()) + "...";
        } else if (avail <= 0) {
            title = "";
        }
        canvas.drawTextOnPath(
                mTextPaint,
                title,
                model.mPath,
                mIsRounded ? 0.0F : titleHorizontalOffset, titleHorizontalOffset
        );
        return titleHorizontalOffset;
    }


    // Reset shadow layer
    private void resetShadowLayer() {
        final float newDx = (float) ((mShadowDistance) * Math.cos((mShadowAngle - mStartAngle) / 180.0F * Math.PI));
        final float newDy = (float) ((mShadowDistance) * Math.sin((mShadowAngle - mStartAngle) / 180.0F * Math.PI));
        if (mIsShadowed) {
            BlurDrawLooper blurDrawLooper = new BlurDrawLooper(mShadowRadius, newDx, newDy, mShadowColor);
            mProgressPaint.setBlurDrawLooper(blurDrawLooper);
        } else {
            mProgressPaint.clearBlurDrawLooper();
        }
    }

    // Set start elevation pin if gradient round progress
    private void setLevelShadowLayer() {
        if (mIsShadowed || mIsLeveled) {
            final float shadowOffset = mShadowRadius * 0.5f;
            BlurDrawLooper blurDrawLooper = new BlurDrawLooper(shadowOffset, 0.0f, -shadowOffset,
                    new Color(adjustColorAlpha(mShadowColor.getValue(), 0.5f)));
            mLevelPaint.setBlurDrawLooper(blurDrawLooper);
        } else {
            mLevelPaint.clearBlurDrawLooper();
        }
    }


    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (!mIsDragged) {
            return false;
        }
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                mActionMoveModelIndex = DISABLE_ANIMATE_INDEX;
                // Get current move angle and check whether touched angle is in sweep angle zone
                float currentAngle = getActionMoveAngle(EventUtil.getXInComponent(component, event),
                        EventUtil.getYIntComponent(component, event));
                if (currentAngle > mSweepAngle && currentAngle < MAX_ANGLE) {
                    break;
                }
                for (int i = 0; i < mModels.size(); i++) {
                    final Model model = mModels.get(i);
                    // Check if our model contains touch points
                    if (model.mBounds.isInclude(EventUtil.getXInComponent(component, event),
                            EventUtil.getYIntComponent(component, event))) {
                        // Check variables for handle touch in progress model zone
                        float modelRadius = model.mBounds.getWidth() * 0.5F;
                        float modelOffset = mProgressModelSize * 0.5F;
                        float mainRadius = mSize * 0.5F;

                        // Get distance between 2 points
                        final float distance = (float) Math.sqrt(Math.pow(EventUtil.getXInComponent(component, event) - mainRadius, 2) +
                                Math.pow(EventUtil.getYIntComponent(component, event) - mainRadius, 2));
                        if (distance > modelRadius - modelOffset && distance < modelRadius + modelOffset) {
                            mActionMoveModelIndex = i;
                            mIsActionMoved = true;
                            handleActionMoveModel(event);
                            animateActionMoveProgress();
                        }
                    }
                }
                break;
            case TouchEvent.POINT_MOVE:
                if (mActionMoveModelIndex == DISABLE_ANIMATE_INDEX && !mIsActionMoved) {
                    break;
                }
                if (mProgressAnimator.isRunning()) {
                    break;
                }
                handleActionMoveModel(event);
                postInvalidate();
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
            case TouchEvent.NONE:
            default:
                // Reset values
                mActionMoveLastSlice = DEFAULT_SLICE;
                mActionMoveSliceCounter = 0;
                mIsActionMoved = false;
                break;
        }
        return true;
    }

    private void handleActionMoveModel(final TouchEvent event) {
        if (mActionMoveModelIndex == DISABLE_ANIMATE_INDEX) {
            return;
        }

        // Get current move angle
        float currentAngle = getActionMoveAngle(EventUtil.getXInComponent(this, event),
                EventUtil.getYIntComponent(this, event));

        // Check if angle in slice zones
        final int actionMoveCurrentSlice;
        if (currentAngle > MIN_ANGLE && currentAngle < POSITIVE_ANGLE) {
            actionMoveCurrentSlice = POSITIVE_SLICE;
        } else if (currentAngle > NEGATIVE_ANGLE && currentAngle < MAX_ANGLE) {
            actionMoveCurrentSlice = NEGATIVE_SLICE;
        } else {
            actionMoveCurrentSlice = DEFAULT_SLICE;
        }

        // Check for handling counter
        if (actionMoveCurrentSlice != 0 &&
                ((mActionMoveLastSlice == NEGATIVE_SLICE && actionMoveCurrentSlice == POSITIVE_SLICE) ||
                        (actionMoveCurrentSlice == NEGATIVE_SLICE && mActionMoveLastSlice == POSITIVE_SLICE))) {
            if (mActionMoveLastSlice == NEGATIVE_SLICE) {
                mActionMoveSliceCounter++;
            } else {
                mActionMoveSliceCounter--;
            }

            // Limit counter for 1 and -1, we don`t need take the race
            if (mActionMoveSliceCounter > 1) {
                mActionMoveSliceCounter = 1;
            } else if (mActionMoveSliceCounter < -1) {
                mActionMoveSliceCounter = -1;
            }
        }
        mActionMoveLastSlice = actionMoveCurrentSlice;

        // Set total traveled angle
        float actionMoveTotalAngle = currentAngle + (MAX_ANGLE * mActionMoveSliceCounter);
        final Model model = mModels.get(mActionMoveModelIndex);

        // Check whether traveled angle out of limit
        if (actionMoveTotalAngle < MIN_ANGLE || actionMoveTotalAngle > MAX_ANGLE) {
            actionMoveTotalAngle =
                    actionMoveTotalAngle > MAX_ANGLE ? MAX_ANGLE + 1.0F : -1.0F;
            currentAngle = actionMoveTotalAngle;
        }

        // Set model progress and invalidate
        float touchProgress = Math.round(MAX_PROGRESS / mSweepAngle * currentAngle);
        model.setProgress(touchProgress);
    }

    // Get the angle of action move model
    private float getActionMoveAngle(final float x, final float y) {
        //Get radius
        final float radius = mSize * 0.5F;

        // Get degrees without offset
        float degrees = (float) ((Math.toDegrees(Math.atan2(y - radius, x - radius)) + 360.0F) % 360.0F);
        if (degrees < 0) {
            degrees += 2.0F * Math.PI;
        }

        // Get point with offset relative to start angle
        final float newActionMoveX =
                (float) (radius * Math.cos((degrees - mStartAngle) / 180.0F * Math.PI));
        final float newActionMoveY =
                (float) (radius * Math.sin((degrees - mStartAngle) / 180.0F * Math.PI));

        // Set new angle with offset
        degrees = (float) ((Math.toDegrees(Math.atan2(newActionMoveY, newActionMoveX)) + 360.0F) % 360.0F);
        if (degrees < 0) {
            degrees += 2.0F * Math.PI;
        }
        return degrees;
    }

    private void postInvalidate() {
        getContext().getUITaskDispatcher().asyncDispatch(new Runnable() {
            @Override
            public void run() {
                ArcProgressStackView.this.invalidate();
            }
        });
    }


    /**
     * 获取进度条动画对象
     *
     * @return AnimatorValue
     */
    public AnimatorValue getProgressAnimator() {
        return mProgressAnimator;
    }

    /**
     * 获取动画时长
     *
     * @return long
     */
    public long getAnimationDuration() {
        return mAnimationDuration;
    }

    /**
     * 设置动画时长
     *
     * @param animationDuration 动画时长
     */
    public void setAnimationDuration(final long animationDuration) {
        mAnimationDuration = (int) animationDuration;
        mProgressAnimator.setDuration(animationDuration);
    }

    /**
     * 获取动画状态监听
     *
     * @return AnimatorValue.StateChangedListener
     */
    public AnimatorValue.StateChangedListener getAnimatorListener() {
        return mAnimatorListener;
    }

    /**
     * 监听动画状态
     *
     * @param animatorListener 接口
     */
    public void setAnimatorListener(final AnimatorValue.StateChangedListener animatorListener) {
        mAnimatorListener = animatorListener;
        mProgressAnimator.setStateChangedListener(animatorListener);
    }

    /**
     * 获取监听数值变化接口
     *
     * @return AnimatorValue.ValueUpdateListener
     */
    public AnimatorValue.ValueUpdateListener getAnimatorUpdateListener() {
        return mAnimatorUpdateListener;
    }

    /**
     * 监听数值变化
     *
     * @param animatorUpdateListener 接口
     */
    public void setAnimatorUpdateListener(final AnimatorValue.ValueUpdateListener animatorUpdateListener) {
        mAnimatorUpdateListener = animatorUpdateListener;
    }

    /**
     * 获取开始的角度
     *
     * @return float
     */
    public float getStartAngle() {
        return mStartAngle;
    }

    /**
     * 设置开始的角度
     *
     * @param startAngle 开始的角度
     */
    public void setStartAngle(float startAngle) {
        mStartAngle = Math.max(MIN_ANGLE, Math.min(startAngle, MAX_ANGLE));
        postInvalidate();
    }

    /**
     * 获取最大的角度
     *
     * @return float
     */
    public float getSweepAngle() {
        return mSweepAngle;
    }

    /**
     * 设置最大的角度
     *
     * @param sweepAngle 最大的角度
     */
    public void setSweepAngle(float sweepAngle) {
        mSweepAngle = Math.max(MIN_ANGLE, Math.min(sweepAngle, MAX_ANGLE));
        postInvalidate();
    }

    /**
     * 获取进度条数组
     *
     * @return 进度条数组
     */
    public List<Model> getModels() {
        return mModels;
    }

    /**
     * 设置进度条数组
     *
     * @param models 进度条数组
     */
    public void setModels(final List<Model> models) {
        mModels.clear();
        mModels = models;
        postInvalidate();
    }

    /**
     * 获取控件的尺寸
     *
     * @return size
     */
    public int getSize() {
        return mSize;
    }

    /**
     * 获取进度条数量
     *
     * @return 进度条数量
     */
    public float getProgressModelSize() {
        return mProgressModelSize;
    }

    /**
     * 获取是否有动画
     *
     * @return boolean
     */
    public boolean isAnimated() {
        return mIsAnimated;
    }

    /**
     * 设置是否有动画
     *
     * @param isAnimated 是否有动画
     */
    public void setIsAnimated(final boolean isAnimated) {
        mIsAnimated = isAnimated;
    }

    /**
     * 获取是否有阴影
     *
     * @return boolean
     */
    public boolean isShadowed() {
        return mIsShadowed;
    }

    /**
     * 设置是否显示阴影
     *
     * @param isShadowed boolean
     */
    public void setIsShadowed(final boolean isShadowed) {
        mIsShadowed = isShadowed;
        postLayout();
        postInvalidate();
    }

    /**
     * 获取是否显示背景
     *
     * @return boolean
     */
    public boolean isModelBgEnabled() {
        return mIsModelBgEnabled;
    }

    /**
     * 设置是否显示背景
     *
     * @param modelBgEnabled boolean
     */
    public void setModelBgEnabled(final boolean modelBgEnabled) {
        mIsModelBgEnabled = modelBgEnabled;
        postInvalidate();
    }

    /**
     * 获取是否显示百分比文字
     *
     * @return boolean
     */
    public boolean isShowProgress() {
        return mIsShowProgress;
    }

    /**
     * 设置是否显示百分比文字
     *
     * @param showProgress 是否显示
     */
    public void setShowProgress(final boolean showProgress) {
        mIsShowProgress = showProgress;
        postInvalidate();
    }

    /**
     * 获取是否是圆角
     *
     * @return boolean
     */
    public boolean isRounded() {
        return mIsRounded;
    }

    /**
     * 设置是否是圆角
     *
     * @param isRounded boolean
     */
    public void setIsRounded(final boolean isRounded) {
        mIsRounded = isRounded;
        if (mIsRounded) {
            mProgressPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
            mProgressPaint.setStrokeJoin(Paint.Join.ROUND_JOIN);
        } else {
            mProgressPaint.setStrokeCap(Paint.StrokeCap.BUTT_CAP);
            mProgressPaint.setStrokeJoin(Paint.Join.MITER_JOIN);
        }
        postInvalidate();
    }

    /**
     * 获取是否可以手动移动进度
     *
     * @return boolean
     */
    public boolean isDragged() {
        return mIsDragged;
    }

    /**
     * 设置是否可以手动移动进度
     *
     * @param isDragged isDragged
     */
    public void setIsDragged(final boolean isDragged) {
        mIsDragged = isDragged;
    }

    /**
     * 进度条圆角的时候，是否在起始位置添加阴影
     *
     * @return 是否有阴影
     */
    public boolean isLeveled() {
        return mIsLeveled;
    }

    /**
     * 进度条圆角的时候，设置是否在起始位置添加阴影
     *
     * @param isLeveled 是否有阴影
     */
    public void setIsLeveled(final boolean isLeveled) {
        mIsLeveled = isLeveled;
        postInvalidate();
    }

    /**
     * 获取动画的曲线类型
     *
     * @return CurveType
     */
    public int getInterpolator() {
        return mProgressAnimator.getCurveType();
    }

    /**
     * 设置动画的曲线类型
     *
     * @param interpolator Animator.CurveType
     */
    public void setInterpolator(final int interpolator) {
        mInterpolator = interpolator == -1 ? Animator.CurveType.ACCELERATE_DECELERATE : interpolator;
        mProgressAnimator.setCurveType(mInterpolator);
    }

    /**
     * 获取进度条之间的间隔
     *
     * @return 间隔
     */
    public float getProgressModelOffset() {
        return mProgressModelOffset;
    }

    /**
     * 设置进度条之间的间隔
     *
     * @param progressModelOffset 间隔
     */
    public void setProgressModelOffset(final float progressModelOffset) {
        mProgressModelOffset = progressModelOffset;
        postInvalidate();
        postLayout();
    }

    /**
     * 获取进度条的总宽度占空间size的比例
     *
     * @return 比例
     */
    public float getDrawWidthFraction() {
        return mDrawWidthFraction;
    }

    /**
     * 设置进度条的总宽度占空间size的比例
     *
     * @param drawWidthFraction 比例
     */
    public void setDrawWidthFraction(float drawWidthFraction) {
        // Divide by half for radius and reset
        mDrawWidthFraction = Math.max(MIN_FRACTION, Math.min(drawWidthFraction, MAX_FRACTION)) * 0.5F;
        mDrawWidthDimension = MIN_FRACTION;
        postInvalidate();
        postLayout();
    }

    /**
     * 获取全部的进度条的总宽度
     *
     * @return 进度条的总宽度
     */
    public float getDrawWidthDimension() {
        return mDrawWidthDimension;
    }

    /**
     * 设置全部的进度条的总宽度
     *
     * @param drawWidthDimension 进度条的总宽度
     */
    public void setDrawWidthDimension(final float drawWidthDimension) {
        mDrawWidthFraction = MIN_FRACTION;
        mDrawWidthDimension = drawWidthDimension;
        postInvalidate();
    }

    /**
     * 获取阴影偏移量
     *
     * @return 阴影偏移量
     */
    public float getShadowDistance() {
        return mShadowDistance;
    }

    /**
     * 设置阴影偏移量
     *
     * @param shadowDistance 阴影偏移量
     */
    public void setShadowDistance(final float shadowDistance) {
        mShadowDistance = shadowDistance;
        postLayout();
        postInvalidate();
    }

    /**
     * 获取阴影角度位置
     *
     * @return 角度位置
     */
    public float getShadowAngle() {
        return mShadowAngle;
    }

    /**
     * 设置角度位置
     *
     * @param shadowAngle 角度位置
     */
    public void setShadowAngle(float shadowAngle) {
        mShadowAngle = Math.max(MIN_ANGLE, Math.min(shadowAngle, MAX_ANGLE));
        postInvalidate();
    }

    /**
     * 获取阴影半径
     *
     * @return 阴影半径
     */
    public float getShadowRadius() {
        return mShadowRadius;
    }

    /**
     * 设置阴影半径
     *
     * @param shadowRadius 阴影半径
     */
    public void setShadowRadius(final float shadowRadius) {
        mShadowRadius = shadowRadius > MIN_SHADOW ? shadowRadius : MIN_SHADOW;
        postInvalidate();
    }

    /**
     * 获取阴影颜色
     *
     * @return 颜色
     */
    public Color getShadowColor() {
        return mShadowColor;
    }

    /**
     * 设置阴影颜色
     *
     * @param shadowColor 颜色
     */
    public void setShadowColor(final Color shadowColor) {
        mShadowColor = shadowColor;
        postInvalidate();
    }

    /**
     * 获取文字颜色
     *
     * @return 颜色对象
     */
    public Color getTextColor() {
        return mTextColor;
    }

    /**
     * 设置文字颜色
     *
     * @param textColor 颜色
     */
    public void setTextColor(final Color textColor) {
        mTextColor = textColor;
        mTextPaint.setColor(textColor);
        postInvalidate();
    }

    /**
     * 获取字体
     *
     * @return Font
     */
    public Font getTypeface() {
        return mTypeface;
    }

    /**
     * 设置字体
     *
     * @param typeface rawfile下的文件名
     */
    public void setTypeface(final String typeface) {
        Font tempTypeface;
        try {
            tempTypeface = createFontBuild(getContext(), typeface);
        } catch (Exception e) {
            tempTypeface = Font.DEFAULT;
        }
        setTypeface(tempTypeface);
    }

    private Font createFontBuild(Context context, String name) {
        ResourceManager resManager = context.getResourceManager();
        RawFileEntry rawFileEntry = resManager.getRawFileEntry("resources/rawfile/" + name);
        Resource resource = null;
        try {
            resource = rawFileEntry.openRawFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        StringBuffer fileName = new StringBuffer(name);
        File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName.toString());
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            int index;
            byte[] bytes = new byte[1024];
            while ((index = resource.read(bytes)) != -1) {
                outputStream.write(bytes, 0, index);
                outputStream.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                resource.close();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        Font.Builder builder = new Font.Builder(file);
        return builder.build();
    }

    /**
     * 设置字体
     *
     * @param typeface Font
     */
    public void setTypeface(final Font typeface) {
        mTypeface = typeface;
        mTextPaint.setFont(typeface);
        postInvalidate();
    }

    /**
     * 获取百分比文字的方向
     *
     * @return IndicatorOrientation
     */
    public IndicatorOrientation getIndicatorOrientation() {
        return mIndicatorOrientation;
    }

    /**
     * 设置百分比文字的方向
     *
     * @param indicatorOrientation IndicatorOrientation
     */
    public void setIndicatorOrientation(final IndicatorOrientation indicatorOrientation) {
        mIndicatorOrientation = indicatorOrientation;
        postInvalidate();
    }


    // Adjust color alpha(used for shadow reduce)
    private int adjustColorAlpha(final int color, final float factor) {
        float[] rgb = ColorUtils.converArgbToRgb(color);
        return Color.argb(Math.round(Color.alpha(color) * factor), (int) rgb[0], (int) rgb[1], (int) rgb[2]);
    }

    /**
     * 开始动画
     */
    public void animateProgress() {
        if (!mIsAnimated || mProgressAnimator == null) {
            return;
        }
        if (mProgressAnimator.isRunning()) {
            if (mAnimatorListener != null) {
                mProgressAnimator.setStateChangedListener(null);
            }
            mProgressAnimator.cancel();
        }
        // Set to animate all models
        mActionMoveModelIndex = ANIMATE_ALL_INDEX;
        mProgressAnimator.setDuration(mAnimationDuration);
        mProgressAnimator.setCurveType(mInterpolator);
        if (mAnimatorListener != null) {
            mProgressAnimator.setStateChangedListener(mAnimatorListener);
        }
        mProgressAnimator.start();
    }

    // Animate progress
    private void animateActionMoveProgress() {
        if (!mIsAnimated || mProgressAnimator == null) {
            return;
        }
        if (mProgressAnimator.isRunning()) {
            return;
        }
        mProgressAnimator.setDuration(DEFAULT_ACTION_MOVE_ANIMATION_DURATION);
        mProgressAnimator.setCurveType(Animator.CurveType.INVALID);
        if (mAnimatorListener != null) {
            mProgressAnimator.setStateChangedListener(null);
        }
        mProgressAnimator.start();
    }


    public static class Model {
        private String mTitle;
        private float mLastProgress;
        private float mProgress;

        private Color mColor;
        private Color mBgColor;
        private Color[] mColors;

        private final RectFloat mBounds = new RectFloat();
        private final Rect mTextBounds = new Rect();

        private final Path mPath = new Path();
        private SweepShader mSweepGradient;

        private final PathMeasure mPathMeasure = new PathMeasure(mPath, false);
        private final float[] mPos = new float[2];
        private final float[] mTan = new float[2];

        public Model(final String title, final float progress, final Color color) {
            init(title, progress);
            setColor(color);
        }

        public Model(final String title, final float progress, final Color[] colors) {
            init(title, progress);
            setColors(colors);
        }

        public Model(final String title, final float progress, final Color bgColor, final Color color) {
            init(title, progress);
            setColor(color);
            setBgColor(bgColor);
        }

        public Model(final String title, final float progress, final Color bgColor, final Color[] colors) {
            init(title, progress);
            setColors(colors);
            setBgColor(bgColor);
        }

        private void init(final String title, final float progress) {
            setTitle(title);
            setProgress(progress);
        }

        public String getTitle() {
            return mTitle;
        }

        public void setTitle(final String title) {
            mTitle = title;
        }

        public float getProgress() {
            return mProgress;
        }


        public void setProgress(float progress) {
            mLastProgress = mProgress;
            mProgress = (int) Math.max(MIN_PROGRESS, Math.min(progress, MAX_PROGRESS));
        }

        public Color getColor() {
            return mColor;
        }

        public void setColor(final Color color) {
            mColor = color;
        }

        public Color getBgColor() {
            return mBgColor;
        }

        public void setBgColor(final Color bgColor) {
            mBgColor = bgColor;
        }

        public Color[] getColors() {
            return mColors;
        }

        public void setColors(final Color[] colors) {
            if (colors != null && colors.length >= 2) {
                mColors = colors;
            } else {
                mColors = null;
            }
        }
    }

    public enum IndicatorOrientation {
        HORIZONTAL, VERTICAL
    }
}
